import json
import threading
import traceback

from gglib.packet import Packet, PacketRejected


class SessionClosedError(BaseException):
    pass


"""
    example: 
        session = Session(None)
        session["start_time"] = datetime.now()
        time = session.start_time
        time = session['start_time']
        
"""


class Session:
    _sessions = dict()

    def __init__(self, sock):
        self.sock = sock
        self._packet_queue = []
        self._lock = threading.RLock()
        Session._sessions[sock.fileno()] = self

    def close(self):
        if self.sock.fileno() in Session._sessions:
            del Session._sessions[self.sock.fileno()]
        self.sock.close()

    def recv_packet(self):
        try:
            data = self.sock.recv(65535)
            # 如果客户端关闭连接
            if not data:
                raise ConnectionError
            json_data = json.loads(data.decode("utf-8"))
            pkt = Packet()
            for key, value in json_data.items():
                pkt[key] = value
            return pkt
        except ConnectionError:
            # 清理客户端套接字对象
            self.close()
            raise SessionClosedError
        except json.decoder.JSONDecodeError:
            traceback.print_exc()

    def route_packet(self, pkt):
        from .middleware import process_packet
        try:
            process_packet(self, pkt, incoming=False)
        # 如果收到PacketRejected异常，则丢弃
        except PacketRejected:
            return
        else:
            self._packet_queue.append(pkt)
            from gglib.io import selector, IO_WRITE, IO_READ, wakeup_selector
            selector.register_event(self.sock.fileno(), IO_WRITE | IO_READ)
            wakeup_selector()

    def next_packet(self):
        if not self._packet_queue:
            return None

        return self._packet_queue.pop(0)

    def __setitem__(self, key, value):
        self.__dict__[key] = value

    def __getitem__(self, item):
        if item in self.__dict__:
            return self.__dict__[item]

    def __enter__(self):
        self._lock.acquire()

    def __exit__(self, exc_type, exc_val, exc_tb):
        self._lock.release()

    @classmethod
    def get_session_by_fd(cls, fd):
        return Session._sessions[fd]
